<!DOCTYPE html>
<html>
<head>
<title>Package File Browser</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<style>
* {
  box-sizing: border-box;
}
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  max-width: 800px;
  margin: 0 auto;
  padding: 1rem;
  color: #333;
}
h1 {
  font-size: 1.5rem;
  margin-bottom: 1rem;
}
input[type="text"] {
  font-size: 16px;
  width: 100%;
  margin-bottom: 0.75rem;
  padding: 0.75rem;
  border: 1px solid #ccc;
  border-radius: 6px;
}
input[type="text"]:focus {
  outline: none;
  border-color: #007aff;
  box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
}
button {
  font-size: 16px;
  padding: 0.75rem 1.25rem;
  background: #007aff;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  width: 100%;
}
button:active {
  background: #005ecb;
}
p {
  font-size: 0.875rem;
  color: #666;
  margin-top: 0.75rem;
}
p code {
  font-size: 0.75rem;
  background: #f0f0f0;
  padding: 0.15rem 0.3rem;
  border-radius: 3px;
  word-break: break-all;
}
#fileList {
  border: 1px solid #e0e0e0;
  border-radius: 8px;
  padding: 0;
  margin-top: 1rem;
  overflow: hidden;
}
#fileList h3 {
  font-size: 0.875rem;
  font-weight: 600;
  margin: 0;
  padding: 0.75rem 1rem;
  background: #f8f8f8;
  border-bottom: 1px solid #e0e0e0;
}
.file-item {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid #eee;
  cursor: pointer;
  font-size: 0.875rem;
  word-break: break-all;
  transition: background 0.1s;
}
.file-item:hover {
  background: #f5f5f5;
}
.file-item:active {
  background: #e8e8e8;
}
.file-item.selected {
  background: #e7f1ff;
  border-bottom: none;
}
.file-content {
  border-bottom: 1px solid #eee;
}
.file-content pre {
  border-top: 1px solid #e0e0e0;
}
pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  background-color: #fafafa;
  padding: 1rem;
  margin: 0;
  max-height: 60vh;
  overflow-y: auto;
  font-size: 0.8125rem;
  line-height: 1.4;
}
.status {
  padding: 1rem;
  color: #666;
  font-size: 0.875rem;
}
@media (min-width: 500px) {
  body {
    padding: 2rem;
  }
  h1 {
    font-size: 1.75rem;
  }
  button {
    width: auto;
  }
}
</style>
</head>
<body>
<h1>Package File Browser</h1>
<input type="text" id="urlInput" placeholder="Package name (e.g. requests) or URL to .whl/.zip file" onkeydown="if(event.key==='Enter')fetchAndUnpackFile()">
<button onclick="fetchAndUnpackFile()">Unpack File</button>
<p>Enter a PyPI package name like <code>requests</code> or <code>llm-mistral</code> (<a href="?package=llm-mistral">try it</a>), or paste a direct URL to a .whl or .zip file.</p>
<div id="fileList" style="display: none"></div>

<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script>
function updateUrl(input) {
    const params = new URLSearchParams();
    if (input.startsWith('http://') || input.startsWith('https://')) {
        params.set('url', input);
    } else {
        params.set('package', input);
    }
    history.replaceState(null, '', '?' + params.toString());
}

async function fetchAndUnpackFile() {
    let input = document.getElementById('urlInput').value.trim();
    const fileListEl = document.getElementById('fileList');

    if (!input) return;

    fileListEl.style.display = 'block';

    // Check if input is a URL or a package name
    let url = input;
    const isUrl = input.startsWith('http://') || input.startsWith('https://');

    if (!isUrl) {
        // Treat as PyPI package name
        fileListEl.innerHTML = `<div class="status">Looking up "${escapeHtml(input)}" on PyPI...</div>`;
        try {
            const pypiResponse = await fetch(`https://pypi.org/pypi/${encodeURIComponent(input)}/json`);
            if (!pypiResponse.ok) {
                fileListEl.innerHTML = `<div class="status">Package "${escapeHtml(input)}" not found on PyPI</div>`;
                return;
            }
            const pypiData = await pypiResponse.json();
            const wheelUrl = pypiData.urls?.find(u => u.url?.endsWith('.whl'));
            if (!wheelUrl) {
                fileListEl.innerHTML = `<div class="status">No wheel (.whl) found for "${escapeHtml(input)}"</div>`;
                return;
            }
            url = wheelUrl.url;
        } catch (error) {
            fileListEl.innerHTML = `<div class="status">Error looking up package: ${escapeHtml(error.message)}</div>`;
            return;
        }
    }

    // Update URL bar
    updateUrl(input);

    fileListEl.innerHTML = '<div class="status">Downloading file...</div>';

    try {
        const response = await fetch(url);
        const arrayBuffer = await response.arrayBuffer();
        const files = await extractFiles(arrayBuffer, url);
        fileListEl.innerHTML = '<h3>Files in package</h3>';
        files.forEach((file, index) => {
            const fileEl = document.createElement('div');
            fileEl.className = 'file-item';
            fileEl.textContent = file.name;
            fileEl.onclick = () => {
                const wasSelected = fileEl.classList.contains('selected');

                // Remove any existing content and selection
                document.querySelectorAll('.file-content').forEach(el => el.remove());
                document.querySelectorAll('.file-item').forEach(el => el.classList.remove('selected'));

                // Toggle off if clicking the same file
                if (wasSelected) {
                    return;
                }

                fileEl.classList.add('selected');

                // Insert content right after the clicked file
                const contentEl = document.createElement('div');
                contentEl.className = 'file-content';
                contentEl.innerHTML = `<pre>${escapeHtml(file.content)}</pre>`;
                fileEl.after(contentEl);
            };
            fileListEl.appendChild(fileEl);
        });
    } catch (error) {
        fileListEl.innerHTML = `<div class="status">Error: ${escapeHtml(error.message)}</div>`;
        console.error(error);
    }
}

function escapeHtml(text) {
    const div = document.createElement('div');
    div.textContent = text;
    return div.innerHTML;
}

async function extractFiles(arrayBuffer, url) {
    const files = [];
    const zip = await JSZip.loadAsync(arrayBuffer);
    for (const [name, file] of Object.entries(zip.files)) {
        const content = await file.async('text');
        files.push({ name, content });
    }
    return files;
}

// Check for query params on page load
const params = new URLSearchParams(window.location.search);
const packageParam = params.get('package');
const urlParam = params.get('url');
if (packageParam || urlParam) {
    document.getElementById('urlInput').value = packageParam || urlParam;
    fetchAndUnpackFile();
}
</script>
</body>
</html>
